﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Xml;
using System.Xml.Serialization;
using NetPublisher.Properties;

namespace NetPublisher
{
    class PublishFileParser
    {
    	private const string SolutionPathPrefix = "@SolutionPath=";
    	private const string FtpServerPrefix = "@FtpServer=";
    	public const string PublishConfigFile = "PublishFiles.txt";

        private PublishFileParser()
        {
            var parentDirectory = new DirectoryInfo(AppDomain.CurrentDomain.BaseDirectory).Parent;
            SolutionFolder = parentDirectory != null ? parentDirectory.FullName : AppDomain.CurrentDomain.BaseDirectory;
        }

    	private readonly List<string> _lines = new List<string>();

    	public string SolutionFolder { get; set; }

    	private readonly List<FtpConnectionString> _ftpServers = new List<FtpConnectionString>();
		/// <summary>
		/// Gets the list of FTP Servers to update.
		/// </summary>
    	public List<FtpConnectionString> FtpServers
    	{
    		get { return _ftpServers; }
    	}

    	public bool DoQuickScan { get; set; }
        
		//public FtpConnectionString FtpConnectionString { get; set; }

        /// <summary>
        /// Indicates that need to ignore C# .cs files. True by default
        /// </summary>
        public bool IgnoreCsFiles { get; set; }

        private readonly List<FileInfo> _files = new List<FileInfo>();
        private readonly HashSet<string> _ignoreFiles = new HashSet<string>();
        private Dictionary<string, PrevFileInfo> _prevFiles; 

        public List<FileInfo> Files
        {
            get { return _files; }
        }

        public long TotalFilesLength { get; set; }

		public static PublishFileParser Create()
		{
            var parser = new PublishFileParser { IgnoreCsFiles = true };

            using (var reader = new StreamReader(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, PublishConfigFile)))
            {
                while (!reader.EndOfStream)
                {
                	parser.AddParserLine(reader.ReadLine());
                }
            }
            return parser;			
		}

    	private void AddParserLine(string line)
    	{
    		if (string.IsNullOrWhiteSpace(line))
    			return;
    		if (line.StartsWith(SolutionPathPrefix, StringComparison.InvariantCultureIgnoreCase))
    			SolutionFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, line.Substring(SolutionPathPrefix.Length));
    		else if (line.StartsWith(FtpServerPrefix, StringComparison.InvariantCultureIgnoreCase))
    			FtpServers.Add(new FtpConnectionString(line));
    		else
    			_lines.Add(line);
    	}

    	/// <summary>
    	/// Parse a settings file PublishFiles.txt. Each line can starts from # - skip, if quick scan. Also line can starts from @ - use recursive. Also you can add files that starting from ^ - excluded files. The order must be #@.
    	/// </summary>
    	/// <param name="quick">Process quick scan. Any files that was started from # will be ignored.</param>
    	/// <param name="ftpServerIndex">The index of the Selected FTP server.</param>
    	/// <returns></returns>
    	public void Parse(bool quick, int ftpServerIndex)
        {
    	    foreach (var line in _lines)
    	    {
    	        try
    	        {
                    ParseFileLine(line);
    	        }
    	        catch (InvalidOperationException ex)
    	        {
    	            if (MessageBox.Show(string.Format(CultureInfo.CurrentCulture, Resources.MessageBoxErrorInvalidLine, line, ex.ToStringWithInnerException()), 
                        Resources.MessageBoxErrorCaption, MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.No)
    	            {
                        Application.Current.Dispatcher.InvokeShutdown();
                        return;
    	            }
    	        }
    	        
    	    }
    	    if (CheckPath(quick, ftpServerIndex)) return;
			LoadPrevFiles(ftpServerIndex);
    		RemoveIgnoredFiles();
            TotalFilesLength = Files.Select(f=>f.Length).Sum();
        }

    	private bool CheckPath(bool quick, int ftpServerIndex)
    	{
    		if (Files.Count != 0) return false;
    		if (MessageBox.Show(Resources.SetupPublishFilesTxt, Resources.SetupPublishFilesTxtCaption, MessageBoxButton.YesNo,
    		                    MessageBoxImage.Question) == MessageBoxResult.Yes)
    		{
    			var process = Process.Start("notepad.exe", Path.Combine(AppDomain.CurrentDomain.BaseDirectory, PublishConfigFile));
    			if (process != null)
    				process.WaitForExit();
    			Parse(quick, ftpServerIndex);
    			return true;
    		}
    		return false;
    	}

        private void RemoveIgnoredFiles()
        {
            _files.RemoveAll(file => _ignoreFiles.Contains(file.FullName));
        }

        private void ParseFileLine(string line)
        {
        	if (string.IsNullOrWhiteSpace(line))
                return;
            if (DoQuickScan && line.StartsWith("#"))
                return;
            line = line.TrimStart('#');
            if (line.IndexOf('*') == -1)
                AddFile(line);
            else
                AddFilePattern(line);
        }

        private void AddFilePattern(string line)
        {
            var recursive = line.StartsWith("@") || !line.StartsWith("!");
            var ignore = line.StartsWith("^");
            var path = Path.Combine(SolutionFolder, line.TrimStart('@', '^'));
            var directoryPath = Path.GetDirectoryName(path);
            if (directoryPath == null)
                throw new InvalidOperationException("The directory is invalid - " + line);
            var directory = new DirectoryInfo(directoryPath);
            if (!directory.Exists)
                throw new InvalidOperationException("The directory is invalid - " + line);
            foreach (var filePath in directory.GetFiles(path.Substring(directoryPath.Length + 1), recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
            {
                if (filePath.FullName.Contains(".svn\\") || filePath.FullName.Contains("_svn\\"))
                    continue;
                if (IgnoreCsFiles && filePath.Extension.ToUpperInvariant() == ".CS")
                    continue;
                AddFileNoCheck(filePath, ignore);
            }
        }

        private void AddFile(string line)
        {
            var ignore = line.StartsWith("^");
            var path = Path.Combine(SolutionFolder, line.TrimStart('^'));
            var fileInfo = new FileInfo(path);
            if (!fileInfo.Exists)
                LogFile.Default.Write("File not found - " + path);
            else AddFileNoCheck(fileInfo, ignore);
        }

        private void AddFileNoCheck(FileInfo fileInfo, bool ignore)
        {
            if (ignore)
            {
                if (!_ignoreFiles.Contains(fileInfo.FullName)) 
                    _ignoreFiles.Add(fileInfo.FullName);
            }
            else
                _files.Add(fileInfo);
        }

        private void LoadPrevFiles(int ftpServerIndex)
        {
            var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format(CultureInfo.InvariantCulture, "prevfiles{0}.xml", ftpServerIndex == 0 ? string.Empty : ftpServerIndex.ToString(CultureInfo.InvariantCulture)));
            var serializer = new XmlSerializer(typeof(List<PrevFileInfo>));
            _prevFiles = TryLoadPrevFiles(filePath, serializer) ?? TryLoadPrevFiles(filePath + ".bak", serializer) 
                ?? TryLoadPrevFiles(filePath + ".bak1", serializer) ?? TryLoadPrevFiles(filePath + ".bak2", serializer) 
                ?? TryLoadPrevFiles(filePath + ".bak3", serializer) ?? TryLoadPrevFiles(filePath + ".bak4", serializer) 
                ?? TryLoadPrevFiles(filePath + ".bak5", serializer);
        }

        private static Dictionary<string, PrevFileInfo> TryLoadPrevFiles(string filePath, XmlSerializer serializer)
        {
            if (!File.Exists(filePath))
                return new Dictionary<string, PrevFileInfo>();
            try
            {
                using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
                {
                    var result = ((List<PrevFileInfo>) serializer.Deserialize(fs));
                    return result.ToDictionary(f => f.FilePath, f=>f);
                }
            }
            catch (InvalidOperationException)
            {
                return null;
            }
            catch (XmlException)
            {
                return null;
            }
        }

        /// <summary>
		/// Save files that was published to enable 'Publish only updated files' functionality.
		/// </summary>
		/// <param name="ftpServerIndex">The index of current ftp server.</param>
        public void SavePrevFiles(int ftpServerIndex)
        {
            //_prevFiles.Clear();
            //_prevFiles = _files.Select(f=>new PrevFileInfo(f)).ToList();
            var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, string.Format(CultureInfo.InvariantCulture, "prevfiles{0}.xml", ftpServerIndex == 0 ? string.Empty : ftpServerIndex.ToString(CultureInfo.InvariantCulture)));
            if (File.Exists(filePath))
            {
                if (File.Exists(filePath + ".bak4"))
                    File.Copy(filePath + ".bak4", filePath + ".bak5", true);
                if (File.Exists(filePath + ".bak3"))
                    File.Copy(filePath + ".bak3", filePath + ".bak4", true);
                if (File.Exists(filePath + ".bak2"))
                    File.Copy(filePath + ".bak2", filePath + ".bak3", true);
                if (File.Exists(filePath + ".bak1"))
                    File.Copy(filePath + ".bak1", filePath + ".bak2", true);
                if (File.Exists(filePath + ".bak"))
                    File.Copy(filePath + ".bak", filePath + ".bak1", true);
                File.Copy(filePath, filePath + ".bak", true);
            }
            var serializer = new XmlSerializer(typeof(List<PrevFileInfo>));
            using (var fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
            {
                serializer.Serialize(fs, _prevFiles.Values.ToList());
            }
        }

        public bool IsFileNew(FileInfo file)
        {
            //var prevFile = _prevFiles.Find(f=>f.FilePath == file.FullName);
            if (!_prevFiles.ContainsKey(file.FullName))
            {
                _prevFiles.Add(file.FullName, new PrevFileInfo(file));
                return true;
            }
            var prevFile = _prevFiles[file.FullName];
            var result = prevFile.IsNewer(file);
            prevFile.Length = file.Length;
            prevFile.LastWriteTime = file.LastWriteTime;
            return result;
        }
    }
}
